local Button = require('src/Widgets/Button')
local Color = require('libs/colorlibrary/colorlibrary')
local Core = require('src/Core')
local Graphics = require('src/graphics')
local i18n = require('libs/i18n')
local Image = require('src/Graphics/Image')
local Label = require('src/Widgets/Label')
local Log = require('src/Log')
local Picture = require('src/Widgets/Picture')
local Snow = require('libs/love-snowflakes/snow')
local State = require('src/State')
local TiledLoader = require('libs/tiledloader/tiledloader')
local Tween = require('libs/tween/tween')
--local Widget = require('src/Widgets/Widget')

-- TODO переместить в snow!
local snowTimer = 0

local MapState = State:derive('MapState')

-- state --
function MapState:load()
    self.screenWidth = love.graphics.getWidth()
    self.screenHeight = love.graphics.getHeight()
    local state = ENGINE.SM:GetState('LocationState')
    self.map = state.map
    -- default values
    zoom = 1
    zoomL = 1
    x = 0
    y = 0

    -- fonts
    -- TODO переделать на более общий шрифт?
    self.FontWidth = g_fontMedium:getWidth('.')
    self.FontHeight = g_fontMedium:getHeight('.')

    local w, h = love.window.getMode()
    -- snow
-- TODO выше есть
    local sw = love.graphics.getWidth()
    local sh = love.graphics.getHeight()
    Snow:load(sw, sh, 3000)

    -- tiles and layers
    self.tiles, self.layers = TiledLoader:Load(self.map)

--print_r(self.tiles[28])
--print_r(self.tiles)
--os.exit(0)

    -- entities
print('UGLY')
    Core:CreateEntities()
    MapState:LoadTilesForEntities(self.map)
--print_r(game.entities)
print(Core:GetEntityInTile(20, 5))
--os.exit(0)

    -- TODO убрать поближе к мыши?
    -- loading cursor tile
    self.tiles.cursorTile = love.graphics.newImage('tiles/128x64/tile_cursor.png')
    self.tiles.cursorTileRed = love.graphics.newImage('tiles/128x64/tile_cursor_red.png')
    self.mouseLastMoveTime = nil
    self.mouseX = nil
    self.mouseY = nil
    self.mousePointerGrid = false

    -- player panel
    -- player panel cover
--    local ppImage = love.graphics.newImage('images/textures/leather_antique_cover_01.png')
    local ppImage = Image('LeatherAntique')
--    local ppImagew = ppImage:getWidth()
--    local ppImageh = ppImage:getHeight()
    local ppImagew = ppImage.w
    local ppImageh = ppImage.h
    self.playerPanel = Picture(0, sh - ppImageh, sw, nil, ppImage.texture, Color.white, 'center', 'center')

    -- player panel info
--    local ppInfoImage = love.graphics.newImage('images/textures/squared_paper_2_a.png')
--    local ppInfoImagew = ppInfoImage:getWidth()
    local ppInfoImage = Image('SquaredPaper')
    local ppInfoImagew = ppInfoImage.w
    self.playerPanelInfo = Picture(sw - ppInfoImagew, 0, nil, ppImageh, ppInfoImage.texture, Color.white, 'right', 'center', true)
    -- TODO
    -- player panel info label temporary
    local ppLabel = Label(10, 10, ppInfoImagew - 40, nil, i18n.translate('Tmp'), g_fontTypeWriterSmall, Color.black, 'left', 'top')

    -- player panel item
--    local ppItemImage = love.graphics.newImage('images/textures/player_panel.png')
--    local ppItemImagew = ppItemImage:getWidth()
    local ppItemImage = Image('PlayerPanel')
    local ppItemImagew = ppItemImage.w
    self.playerPanelItem = Picture((sw - ppItemImagew)/2, 0, nil, ppImageh, ppItemImage.texture, Color.white, 'right', 'center', true)
    -- TODO
    -- knife
--    local ppItemImageTMP = love.graphics.newImage('images/inventory/item_knife_01.png')
--    local ppItemImagewTMP = ppItemImageTMP:getWidth()
--    local ppItemImagehTMP = ppItemImageTMP:getHeight()
    local ppItemImageTMP = Image('ItemKnife')
    local ppItemImagewTMP = ppItemImageTMP.w
    local ppItemImagehTMP = ppItemImageTMP.h
    self.playerPanelItemTMP = Picture((sw - ppItemImagewTMP)/2, sh - ppItemImagehTMP - 30, nil, ppImagehTMP, ppItemImageTMP.texture, Color.white, 'right', 'center', true)

    -- player panel combining
    self.playerPanel:addChild(self.playerPanelInfo)
    self.playerPanelInfo:addChild(ppLabel)
    self.playerPanel:addChild(self.playerPanelItem)

    -- sounds
    --    self.soundMove = love.audio.newSource('sounds/pick.ogg', 'static')
    --    self.soundAccept = love.audio.newSource('sounds/accept.ogg', 'static')

    -- sounds setup for menu
    --    menuMain:setSndMove(self.soundMove)
    --    menuMain:setSndSuccess(self.soundAccept)

    -- graphics
    -- outline shader
    local shader = 'libs/shaders/outline/outline.glsl'
    Log:debug('Loading outline shader: '..shader)
    -- TODO move it somewhere
    g_shader = love.graphics.newShader(shader)

    -- buffers
    self.Buffers = {}
    self.Buffers.RebuildGround = true
    self.Buffers.RebuildObjects = true
    self.Buffers.Ground = love.graphics.newCanvas(self.screenWidth, self.screenHeight)
    self.Buffers.Objects = love.graphics.newCanvas(self.screenWidth, self.screenHeight)
    Log:debug('Memory used (in kB): ' .. math.floor(collectgarbage('count')))

    -- events
    ENGINE.EVENTS:hook('key_pressed', self.keyboard)
    ENGINE.EVENTS:hook('onMouseClick', self.mouseClick)
end


function MapState:LoadTilesForEntities(map)
    local tiles = {}
    local s = ENGINE.SM:GetTopState()
    for k, v in pairs(game.entities) do
        if v.where == s.name then
            Log:debug('Loading tileset for entity: '..k)
            v.tiles = TiledLoader:ParseTileset(v.tileset)
print_r(v)
        end
    end
end

function MapState:update(dt)
    self.updateMouse(self, dt)
    -- snow update
    --    ENGINE.SM:update(dt)
    snowTimer = snowTimer + dt
    if snowTimer > 0.02 then
        Snow:update(dt)
        snowTimer = 0
    end
    --    menuMain:update()
    --    if ENGINE.EVENTS.events['f10'] then
    --        ENGINE.EVENTS.events['f10'] = nil
    --        -- TODO общий выход
    --        love.event.quit(0)
    --    end
    -- tween
    --    self.cover:update(dt)
    --    self.buttonStartGame:update(dt)
    if love.keyboard.isDown("left") then
        x = x + 900*dt
        self.Buffers.RebuildGround = true
        self.Buffers.RebuildObjects = true
    end
    if love.keyboard.isDown("right") then
        x = x - 900*dt
        self.Buffers.RebuildGround = true
        self.Buffers.RebuildObjects = true
    end
    if love.keyboard.isDown("up") then
        y = y+900*dt
        self.Buffers.RebuildGround = true
        self.Buffers.RebuildObjects = true
    end
    if love.keyboard.isDown("down") then
        y = y-900*dt
        self.Buffers.RebuildGround = true
        self.Buffers.RebuildObjects = true
    end
    local zoomPrev = zoomL
    zoomL = lerp(zoomL, zoom, 0.05*(dt*300))
    if not (zoomL == zoomPrev) then
        self.Buffers.RebuildGround = true
        self.Buffers.RebuildObjects = true
--        self.objectUnderMouseLabel = nil
    end

    -- player panel
    self.playerPanel:update(dt)
end

-- TODO move it?
function lerp(a, b, rate) --EMPLOYEE OF THE MONTH
    local result = (1 - rate) * a + rate * b
    return result
end


function MapState:draw()
--    local state = ENGINE.SM:GetTopState()
    -- buffers
    if self.Buffers.RebuildGround or self.Buffers.RebuildObjects then
        self.buildBuffers(self, x, y, zoomL, self.tiles, self.layers, self.screenWidth, self.screenHeight)
        self.Buffers.RebuildGround = false
        self.Buffers.RebuildObjects = false
    end
    -- draw canvas
    love.graphics.setColor(1, 1, 1, 1)
--    love.graphics.setBlendMode('alpha', 'premultiplied')
    love.graphics.setBlendMode('alpha')
    love.graphics.draw(self.Buffers.Ground, 0, 0)
    love.graphics.draw(self.Buffers.Objects, 0, 0)

    love.graphics.setColor(1, 0, 1, 1)
    info = love.graphics.getStats()
--    love.graphics.print("FPS: "..love.timer.getFPS())
    love.graphics.print("Draw calls: "..info.drawcalls, 0, 25)
--    love.graphics.print("Texture memory: "..((info.texturememory/1024)/1024).."mb", 0, 50)
--    love.graphics.print("Zoom level: "..zoom, 0, 75)
--    local state = ENGINE.SM:GetTopState()
    local x = self.tileUnderCursorx
    local y = self.tileUnderCursory
    if x and y then
        love.graphics.print("X: "..math.floor(x).." Y: "..math.floor(y), 0, 100)
    end

    -- draw snow
--    Snow:draw()

--    -- draw player panel
--    self.playerPanel:draw()
--    self.playerPanelItemTMP:draw()

    -- floating label
    if self.objectUnderMouseLabel then
        self.objectUnderMouseLabel:draw()
    end

    Graphics:ShowFPS()
end


function MapState:buildBuffers(xOff, yOff, zoom, tiles, layers)
    -- clear canvas
    if self.Buffers.RebuildGround then
        love.graphics.setCanvas(self.Buffers.Ground)
        love.graphics.clear()
    end
    if self.Buffers.RebuildObjects then
        love.graphics.setCanvas(self.Buffers.Objects)
        love.graphics.clear()
    end

    local mouse = ENGINE.MOUSE
    local mapTileWidth = layers.mapTileWidth
    local mapTileHeight = layers.mapTileHeight

    local screenWidth = love.graphics.getWidth()
    local screenHeight = love.graphics.getHeight()

    love.graphics.setColor(1, 1, 1, 1)

    local layer = 1
    local width = #layers[layer][1]
    local height = #layers[layer]

    self.tileUnderCursorx = nil
    self.tileUnderCursory = nil

    for layer = 1, #layers do
        self.objectUnderCursorx = nil
        self.objectUnderCursory = nil
        self.objectUnderCursorLayer = nil

        --width = 5
        --height = 1

        for i = 1, width - 1 do
            for j = 1, height - 1 do
                local id
                local tile = nil
                -- get tile to draw
                id = layers[layer][i][j]
                if tiles[id] then
                    tile = tiles[id]
                end
                if not (layer == 1) then
                    -- not ground layer
                    local e = Core:GetEntityInTile(i, j)
                    if e then
                        tile = Core:GetTile(e)
                    end
                end

                if tile then
                    local texture, imagedata
                    local realWidth, realHeight
                    local x, y
                    local needDraw = false

                    texture = tile.texture
                    imagedata = tile.imagedata
                    realWidth = tonumber(tile.w)
                    realHeight = tonumber(tile.h)
                    -- get tileoffset for tileset if there is any
                    local tileOffsetX = 0
                    local tileOffsetY = 0
                    if tile.tileoffsetx or tile.tileoffsety then
                        tileOffsetX = tile.tileoffsetx
                        tileOffsetY = tile.tileoffsety
                    end

                    -- converting coodrinates from map to screen
                    --local x = (i - j) * (mapTileWidth / 2 * zoom) + (tileOffsetX + realWidth) * zoom      + realWidth * zoom
                    --local y = (i + j) * (mapTileHeight / 2 * zoom) + (tileOffsetY) * zoom      + realHeight * zoom
                    x = (i - j) * (mapTileWidth / 2 * zoom) + (tileOffsetX + realWidth) * zoom
                    y = (i + j) * (mapTileHeight / 2 * zoom) + (tileOffsetY) * zoom
                    -- coordinates correction due to correspond to Tiled
                    if realWidth > mapTileWidth then
                        x = x - (realWidth - mapTileWidth)*zoom
                    end
                    if realHeight > mapTileHeight then
                        y = y - (realHeight - mapTileHeight)*zoom
                    end

                    -- check is sprite visible on screen
                    local xs = x + xOff + mapTileWidth*zoom
                    local ys = y + yOff+ mapTileHeight*zoom
                    local xe = xs + realWidth
                    local ye = ys + realHeight
                    if (xe > 0) then
                        if (ye > 0) then
                            if (xs < screenWidth + mapTileWidth * zoom) then
                                if (ys < screenHeight + mapTileHeight * zoom) then
                                    needDraw = true
                                end
                            end
                        end
                    end

                    -- drawing tile only if it is visible
                    if needDraw then
                        love.graphics.setBlendMode('alpha')
                        -- canvas
                        if (layer == 1) and self.Buffers.RebuildGround then
                            love.graphics.setCanvas(self.Buffers.Ground)
                            love.graphics.draw(texture, x + xOff, y + yOff, 0, zoom, zoom)
                        end
                        if layer > 1 then
                            if self.Buffers.RebuildObjects then
                                love.graphics.setCanvas(self.Buffers.Objects)
                                love.graphics.draw(texture, x + xOff, y + yOff, 0, zoom, zoom)
                            end
                        end

                        if self.spriteIsUnderMouse(self, imagedata, x + xOff, y + yOff, realWidth, realHeight, screenWidth, screenHeight, zoom) then
                            if layer == 1 then
                                -- remember ground tile under cursor
                                self.tileUnderCursorx = i
                                self.tileUnderCursory = j
                            else
                                if self.mousePointerGrid then
                                    self.objectUnderCursorx = nil
                                    self.objectUnderCursory = nil
                                    self.objectUnderCursorLayer = nil
                                else
                                    -- remember sprite under cursor
                                    self.objectUnderCursorx = i
                                    self.objectUnderCursory = j
                                    self.objectUnderCursorLayer = layer
                                end
                            end
                        end
                        -- draw mouse grid pointer
                        if self.mousePointerGrid and layer == 1 then
                            if (i == self.tileUnderCursorx) and (j == self.tileUnderCursory) then
                                love.graphics.draw(tiles.cursorTile, x + xOff, y + yOff, 0, zoom, zoom)
                            end
                        end
                    end
                    -- drawing tile end
                end
                -- if tile end
            end
            -- j loop end
        end
        -- i loop end
    end
    -- layers loop end

    -- drawing outline object
    --if (layer > 1) then
    if self.objectUnderCursorx and self.objectUnderCursory then
        local tile

        self.Buffers.RebuildObjects = true
        local i = self.objectUnderCursorx
        local j = self.objectUnderCursory
        local layer = self.objectUnderCursorLayer
        local id = layers[layer][i][j]

        tile = tiles[id]

        if not tile then
            -- tile is object, it is not on map
            local e = Core:GetEntityInTile(i, j)
            if e then
                tile = Core:GetTile(e)
            end
        end

        local texture = tile.texture
        local source = tile.source
        local realWidth = tonumber(tile.w)
        local realHeight = tonumber(tile.h)

        local tileOffsetX = 0
        local tileOffsetY = 0
        if tile.tileoffsetx or tile.tileoffsety then
            tileOffsetX = tile.tileoffsetx
            tileOffsetY = tile.tileoffsety
        end

        local x = (i - j) * (mapTileWidth / 2 * zoom) + (tileOffsetX + realWidth) * zoom
        local y = (i + j) * (mapTileHeight / 2 * zoom) + (tileOffsetY) * zoom

        -- coordinates correction due to correspond to Tiled
        if realWidth > mapTileWidth then
            x = x - (realWidth - mapTileWidth)*zoom
        end
        if realHeight > mapTileHeight then
            y = y - (realHeight - mapTileHeight)*zoom
        end

        g_shader:send('stepSize', {3/texture:getWidth(), 3/texture:getHeight()})
        love.graphics.setShader(g_shader)
        love.graphics.draw(texture, x + xOff, y + yOff, 0, zoom, zoom)
        love.graphics.setShader()

        self.objectUnderMouse = tile.path
    end
    -- drawing outline object END
    --end

    love.graphics.setCanvas()

    return self
end

function MapState:spriteIsUnderMouse(imagedata, xs, ys, width, height, screenWidth, screenHeight, zoom)
    -- NOTE: ImageData in love goes from 0, 0 to width-1, height-1
    local result = false
    local mx, my = love.mouse.getPosition()
    if (mx > xs) and (mx < xs + width * zoom) then
        if (my > ys) and (my < ys + height * zoom) then
            local x = (mx - xs)/zoom
            local y = (my - ys)/zoom
            if ((x > 0) and (x < imagedata:getWidth())) then
                if ((y>0) and (y < imagedata:getHeight())) then
                    local r, g, b, a = imagedata:getPixel(x, y)
                    if (a > 0) then
                        result = true
                    end
                end
            end
        end
    end
    return result
end

function MapState:exit()
    -- events
    ENGINE.EVENTS:unhook('key_pressed', self.keyboard)
    ENGINE.EVENTS:unhook('onMouseClick', self.mouseClick)
end


--function love.keypressed(key, scancode, isrepeat)
--    menuSystem.keypressed(scancode)
--
--    if scancode == 'return' then
--        print('A')
--        love.event.quit()
--    end
--end


--function love.mousemoved(x, y, dx, dy, istouch)
--    menuSystem.mousemoved(x, y)
--end

--function love.mousemoved(x, y, dx, dy, istouch)
function MapState:updateMouse(dt)
    local mx, my = love.mouse.getPosition()
    if not ((self.mouseX == mx) and (self.mouseY == my)) then
        -- mouse moved
        self.mouseX = mx
        self.mouseY = my
        self.mouseLastMoveTime = love.timer.getTime()
        self.objectUnderMouse = nil
        self.objectUnderMousePrevious = nil
        self.objectUnderMouseLabel = nil
        -- TODO ?
--        local state = ENGINE.SM:GetTopState()
        self.Buffers.RebuildObjects = true
    else
        if (love.timer.getTime() > self.mouseLastMoveTime + 1) then
            -- update floating label
            if self.objectUnderMouse then
                if self.objectUnderMousePrevious == self.objectUnderMouse then
                    -- same object as before
                else
                    -- new object
                    self.objectUnderMousePrevious = self.objectUnderMouse
                    self.objectUnderMouseLabel = Label(mx, my - 30, nil, nil, self.objectUnderMouse, g_fontTypeWriterSmall, Color.white, 'left', 'center', Color.black)
                end
            end
        end
    end
--    print(self.mx, self.my)
    -- check for player panel coordinates
--    if (my > MapState.screenHeight - MapState.playerPanel.h) then
--    if (my > self.screenHeight - self.playerPanel.h) then
    if (my > self.screenHeight - 200) then
--        cursor = love.mouse.newCursor('images/cursor/cursor_plain2.png', 0, 0)
        love.mouse.setCursor()
    else
--        cursor = love.mouse.newCursor('images/cursor/cursor_plain2.png', 0, 0)
--        love.mouse.setCursor(cursor)
        ENGINE.MOUSE:resetCursor()
    end
end

function MapState:keyboard()
    if love.keyboard.isDown('escape') then
        if ENGINE.SM:IsCurrentState('MapState') then
            local state = ENGINE.SM:GetTopState()
            state:setCursorPlain()
            ENGINE.SM:InitState('ExitConfirmState', 'ExitConfirmState')
            ENGINE.SM:Switch('ExitConfirmState', true)
        end
    end
end

function MapState:setCursorPlain()
--    local state = ENGINE.SM:GetTopState()
    local state = self
    state.mousePointerGrid = false
    love.mouse.setVisible(true)
end

function MapState:setCursorGrid()
--    local state = ENGINE.SM:GetTopState()
    local state = self
    state.mousePointerGrid = true
    love.mouse.setVisible(false)
end

function MapState:mouseClick()
    local button = self
    -- primary click (left mouse button)
    if button == '1' then
        if state.mousePointerGrid then
            -- move player here
        end
    end
    -- secondary click (right mouse button)
    if button == '2' then
        if ENGINE.SM:IsCurrentState('MapState') then
            local state = ENGINE.SM:GetTopState()
            state.Buffers.RebuildObjects = true
            if state.mousePointerGrid then
                state:setCursorPlain()
            else
                state:setCursorGrid()
            end
        end
    end
end

-- ----------

-- TODO move it somewhere
function love.wheelmoved(x, y)
    if y > 0 then
        zoom = zoom + 0.1
    elseif y < 0 then
        zoom = zoom - 0.1
    end

    if zoom < 0.1 then
        zoom = 0.1
    end
    if zoom > 5 then
        zoom = 5
    end
end

return MapState
